package cn.good.yan.service.t2;

import cn.good.yan.service.t2.mapper.UserMapper;
import cn.good.yan.service.t2.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * 2. 缓存双写
 *    延迟双删除
 *
 * @author shengren.yan
 * @create 2023-11-12
 */
@Service
@Slf4j
public class SxService {

    public static final String CACHE_KEY_USER = "user:";

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate redisTemplate;


    /**
     * 目前的代码对于一般代码可以，但多线程情况下不行，会有很多线程，大量访问到mysql数据库，会导致数据库压力大
     *
     * @param id
     * @return
     */
    public User findUserById(Long id){
        User user = null;
        String key = CACHE_KEY_USER + id;
        // 1.先从redis中查询，如果有直接返回结果，没有再去查询 mysql
        user = (User) redisTemplate.opsForValue().get(key);
        if (user == null){
            // 2. redis中没有,查询mysql
            user = userMapper.selectById(id);
            if (user == null){
                // 3.1 redis + mysql 都无数据 - 防止多次穿透，可以记录该key保存到redis中
                return user;
            }else {
                // 3.2 mysql有，需要回写到redis，保证下一次的缓存命中率
                redisTemplate.opsForValue().set(key,user);
            }
        }
        return user;
    }

    /**
     * 使用双检加锁，解决上面问题  - 此时还是有问题，就是效率不高，不适用微服务
     * @param id
     * @return
     */
    public User findUserById2(Long id){
        User user = null;
        String key = CACHE_KEY_USER + id;
        // 1.先从redis里面查询，如果有直接返回结果，如果没有再去查询mysql
        // 第一次查询redis
        user = (User) redisTemplate.opsForValue().get(key);
        if (user == null){
            // 2.对于高QPS的优化，进来就先加锁，保证一个请求操作，让外面的redis等待一下，避免击穿mysql
            synchronized (SxService.class){
                // 第二次查询redis，加锁后
                user = (User) redisTemplate.opsForValue().get(key);
                // 3. 二次查redis还是null，可以去查mysql了(mysql默认有数据)
                if (user == null) {
                    // 4 查询mysql拿数据(mysql默认有数据)
                    user = userMapper.selectById(id);
                    if (user == null) {
                        return null;
                    } else {
                        // 5. mysql里面有数据的，需要回写redis，完成数据一致性的同步工作
                        //    setIfAbsent 如果有再回写
                        redisTemplate.opsForValue().setIfAbsent(key, user, 7L, TimeUnit.DAYS);
                    }
                }
            }
        }
        return user;
    }

    /**
     * 延迟双删除
     *
     * @param user
     * @return
     */
    public User save(User user){
        String key = CACHE_KEY_USER + user.getId();
        // 1. 第一次删除缓存
        redisTemplate.delete(key);
        // 2. 修改数据库
        userMapper.updateById(user);
        // 3.延迟 睡眠  ：更新完数据库的时间 + sleep的时间 大于 读取数据并写入换的时间
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 4. 第二次删除缓存
        redisTemplate.delete(key);
        // 4. 第二次删除缓存 - 优化 - 可创建新的线程，执行，这样会增加吞吐量
        return user;
    }

}
